<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>方法调用树列表</title>
  <!-- 引入依赖 -->
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css">
  <script src="https://unpkg.com/element-plus"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script src="https://d3js.org/d3.v7.min.js"></script>

  <style>
    /* 基础样式保持不变，新增容错样式 */
    body {
      margin: 0;
      padding: 20px;
      background-color: #f5f7fa;
      font-family: "Helvetica Neue", Arial, sans-serif;
    }
    .container {
      max-width: 1400px;
      margin: 0 auto;
    }
    .query-form {
      background: #fff;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
      margin-bottom: 20px;
    }
    .form-row {
      display: flex;
      flex-wrap: wrap;
      gap: 16px;
      margin-bottom: 16px;
    }
    .form-item {
      flex: 1;
      min-width: 200px;
    }
    .table-container {
      background: #fff;
      border-radius: 8px;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
      padding: 20px;
    }
    .tree-dialog .el-dialog__body {
      max-height: 700px;
      overflow: auto;
      padding: 0;
      position: relative;
    }
    #tree-container {
      width: 100%;
      height: 650px;
      overflow: auto;
    }
    .tree-tooltip, .link-tooltip {
      position: absolute;
      background: rgba(0, 0, 0, 0.8);
      color: white;
      padding: 8px 12px;
      border-radius: 4px;
      font-size: 12px;
      pointer-events: none;
      z-index: 1000;
      max-width: 400px;
      box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
    }
    .link-tooltip {
      max-width: 300px;
    }
    .copy-tooltip {
      position: absolute;
      background: rgba(0, 150, 136, 0.9);
      color: white;
      padding: 4px 8px;
      border-radius: 4px;
      font-size: 12px;
      pointer-events: none;
      z-index: 1001;
      animation: fadein 0.5s, fadeout 0.5s 2.5s;
    }
    @keyframes fadein { from { opacity: 0; } to { opacity: 1; } }
    @keyframes fadeout { from { opacity: 1; } to { opacity: 0; } }
    .node {
      cursor: pointer;
    }
    .node-circle {
      stroke: #fff;
      stroke-width: 2px;
      transition: r 0.2s;
      /* 确保节点可见的默认样式 */
      fill: #999; /* 默认颜色，防止无类型节点不可见 */
    }
    .node-circle:hover {
      r: 12;
    }
    .node-success {
      fill: #67c23a;
    }
    .node-error {
      fill: #f56c6c;
    }
    .node-sql {
      fill: #409eff;
    }
    .node-text {
      font-size: 12px;
      fill: #333;
      cursor: copy;
      white-space: nowrap;
      transition: fill 0.2s;
    }
    .node-text:hover {
      fill: #409eff;
      text-decoration: underline;
    }
    .node-collapse-icon {
      font-size: 10px;
      fill: #666;
      cursor: pointer;
    }
    .link {
      fill: none;
      stroke-width: 1.8px;
      transition: stroke-width 0.2s;
      cursor: help;
      stroke: #ccc; /* 默认连线颜色，防止无类型连线不可见 */
    }
    .link:hover {
      stroke-width: 2.5px;
    }
    .link-async {
      stroke-dasharray: 6,4;
    }
    /* 其他样式保持不变 */
    .param-table {
      width: 100%;
      border-collapse: collapse;
    }
    .param-table th, .param-table td {
      border: 1px solid #ebeef5;
      padding: 8px 12px;
      text-align: left;
    }
    .param-table th {
      background-color: #f5f7fa;
      font-weight: 500;
    }
    .tree-controls {
      padding: 10px 20px;
      background: #f5f7fa;
      border-bottom: 1px solid #e4e7ed;
      display: flex;
      flex-wrap: wrap;
      justify-content: space-between;
      align-items: center;
      gap: 10px;
    }
    .zoom-controls button {
      margin-right: 5px;
    }
    .legend {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: 15px;
      font-size: 12px;
    }
    .legend-item {
      display: flex;
      align-items: center;
      gap: 5px;
    }
    .legend-color {
      width: 12px;
      height: 12px;
      border-radius: 50%;
    }
    .legend-line {
      width: 15px;
      height: 2px;
    }
    .thread-legend {
      margin-top: 10px;
      padding-top: 10px;
      border-top: 1px dashed #e4e7ed;
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      font-size: 11px;
    }
    .thread-legend-item {
      display: flex;
      align-items: center;
      gap: 4px;
      padding: 2px 6px;
      background: rgba(255, 255, 255, 0.8);
      border-radius: 4px;
    }
    .thread-color {
      width: 8px;
      height: 8px;
      border-radius: 50%;
    }
    .sql-content {
      white-space: pre-wrap;
      word-wrap: break-word;
      font-family: monospace;
      font-size: 13px;
    }
    .detail-table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 10px;
    }
    .detail-table th {
      width: 30%;
      background-color: #f5f7fa;
      text-align: right;
      padding: 8px 12px;
      font-weight: 500;
    }
    .detail-table td {
      width: 70%;
      padding: 8px 12px;
      text-align: left;
      word-break: break-all;
    }
    .detail-table th, .detail-table td {
      border: 1px solid #ebeef5;
    }
    .success-status {
      color: #67c23a;
    }
    .error-status {
      color: #f56c6c;
    }
  </style>
</head>
<body>
<div id="app" class="container">
  <!-- 查询表单 -->
  <div class="query-form">
    <h2>方法调用树查询</h2>
    <el-form :inline="false" ref="queryForm" :model="queryParams">
      <div class="form-row">
        <el-form-item label="服务类名" class="form-item">
          <el-input v-model="queryParams.serviceName" placeholder="请输入服务类名"></el-input>
        </el-form-item>
        <el-form-item label="方法名" class="form-item">
          <el-input v-model="queryParams.methodName" placeholder="请输入方法名"></el-input>
        </el-form-item>
        <el-form-item label="全局TraceId" class="form-item">
          <el-input v-model="queryParams.traceId" placeholder="请输入全局唯一traceId"></el-input>
        </el-form-item>
      </div>

      <div class="form-row">
        <el-form-item label="开始时间" class="form-item">
          <el-date-picker
                  v-model="dateRange"
                  type="datetimerange"
                  range-separator="至"
                  start-placeholder="开始时间"
                  end-placeholder="结束时间"
                  @change="handleDateChange"
          ></el-date-picker>
        </el-form-item>
<!--        <el-form-item label="分页参数" class="form-item">-->
<!--          <el-select v-model="queryParams.size" placeholder="每页条数">-->
<!--            <el-option label="10条/页" value="10"></el-option>-->
<!--            <el-option label="20条/页" value="20"></el-option>-->
<!--            <el-option label="50条/页" value="50"></el-option>-->
<!--            <el-option label="100条/页" value="100"></el-option>-->
<!--          </el-select>-->
<!--        </el-form-item>-->
      </div>

      <div class="form-row">
        <el-form-item>
          <el-button type="primary" @click="handleQuery">查询</el-button>
          <el-button @click="resetQuery">重置</el-button>
          <el-button type="primary" @click="collectStats">刷新</el-button>
        </el-form-item>
      </div>
    </el-form>
  </div>

  <!-- 列表展示 -->
  <div class="table-container">
    <el-table
            :data="tableData"
            border
            style="width: 100%"
            v-loading="loading"
    >
      <el-table-column prop="traceId" label="全局TraceId" width="200"></el-table-column>
      <el-table-column prop="path" label="节点路径" width="200"></el-table-column>
      <el-table-column prop="threadName" label="线程名称"></el-table-column>
      <el-table-column prop="serviceName" label="服务类名"></el-table-column>
      <el-table-column prop="methodName" label="方法名"></el-table-column>
      <el-table-column prop="startTime" label="开始时间">
        <template #default="scope">
          {{ formatDateTime(scope.row.startTime) }}
        </template>
      </el-table-column>
      <el-table-column prop="costTime" label="执行耗时(ms)">
        <template #default="scope">
            <span :class="scope.row.costTime > 1000 ? 'error-status' : ''">
              {{ scope.row.costTime }}ms
            </span>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="200">
        <template #default="scope">
          <el-button
                  type="text"
                  size="small"
                  @click="handleViewParams(scope.row)"
          >
            查看参数
          </el-button>
          <el-button
                  type="text"
                  size="small"
                  @click="handleViewTrace(scope.row.traceId)"
          >
            查看调用链
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <div style="margin-top: 16px; text-align: right;">
      <el-pagination
              @size-change="handleSizeChange"
              @current-change="handleCurrentChange"
              :current-page="currentPage"
              :page-sizes="[10, 20, 50, 100]"
              :page-size="queryParams.size"
              layout="total, sizes, prev, pager, next, jumper"
              :total="totalCount"
      ></el-pagination>
    </div>
  </div>

  <!-- 调用链弹窗 (D3树形图) -->
  <el-dialog
          title="调用链详情"
          v-model="traceDialogVisible"
          width="90%"
          class="tree-dialog"
          @close="cleanupTree"
  >
    <div class="tree-controls">
      <div>
        <div class="legend">
          <div class="legend-item">
            <div class="legend-color" style="background-color: #67c23a;"></div>
            <span>成功调用</span>
          </div>
          <div class="legend-item">
            <div class="legend-color" style="background-color: #f56c6c;"></div>
            <span>失败调用</span>
          </div>
          <div class="legend-item">
            <div class="legend-color" style="background-color: #409eff;"></div>
            <span>SQL执行</span>
          </div>
          <div class="legend-item">
            <div class="legend-line" style="background-color: #ccc;"></div>
            <span>同步调用</span>
          </div>
          <div class="legend-item">
            <div class="legend-line" style="background-color: #ccc; border-top: 2px dashed #ccc; height: 0;"></div>
            <span>异步调用</span>
          </div>
        </div>
        <!-- 线程颜色图例 -->
        <div class="thread-legend" ref="threadLegend">
          <!-- 动态生成线程颜色图例 -->
        </div>
      </div>
      <div class="zoom-controls">
        <el-button type="text" size="small" @click="zoomIn">放大</el-button>
        <el-button type="text" size="small" @click="zoomOut">缩小</el-button>
        <el-button type="text" size="small" @click="resetZoom">重置视图</el-button>
      </div>
    </div>
    <div id="tree-container"></div>
    <div ref="tooltip" class="tree-tooltip" style="display: none;"></div>
    <div ref="linkTooltip" class="link-tooltip" style="display: none;"></div>
    <div ref="copyTooltip" class="copy-tooltip" style="display: none;">已复制到剪贴板</div>

    <template #footer>
      <el-button @click="traceDialogVisible = false">关闭</el-button>
    </template>
  </el-dialog>

  <!-- 节点详情弹窗 -->
  <el-dialog
          title="节点详情"
          v-model="nodeDetailVisible"
          width="70%"
          :fullscreen="false"
  >
    <div v-if="currentNodeData">
      <table class="detail-table">
        <tr>
          <th>全局TraceId</th>
          <td>{{ currentNodeData.traceId }}</td>
        </tr>
        <tr>
          <th>节点路径</th>
          <td>{{ currentNodeData.path || '无' }}</td>
        </tr>
        <tr>
          <th>当前节点spanId</th>
          <td>{{ currentNodeData.spanId }}</td>
        </tr>
        <tr>
          <th>父节点spanId</th>
          <td>{{ currentNodeData.parentSpanId || '无' }}</td>
        </tr>
        <tr>
          <th>线程名称</th>
          <td>{{ currentNodeData.threadName }}</td>
        </tr>
        <tr>
          <th>调用深度</th>
          <td>{{ currentNodeData.depth }}</td>
        </tr>
        <tr>
          <th>开始时间</th>
          <td>{{ formatDateTime(currentNodeData.startTime) }}</td>
        </tr>
        <tr>
          <th>结束时间</th>
          <td>{{ formatDateTime(currentNodeData.endTime) }}</td>
        </tr>
        <tr>
          <th>执行耗时</th>
          <td>{{ currentNodeData.costTime }}ms</td>
        </tr>
        <tr>
          <th>服务类名</th>
          <td>{{ currentNodeData.serviceName }}</td>
        </tr>
        <tr>
          <th>方法名</th>
          <td>{{ currentNodeData.methodName }}</td>
        </tr>
        <tr v-if="currentNodeData.sqlId">
          <th>SQL ID</th>
          <td>{{ currentNodeData.sqlId }}</td>
        </tr>
        <tr>
          <th>执行结果</th>
          <td>
              <span :class="currentNodeData.success ? 'success-status' : 'error-status'">
                {{ currentNodeData.success ? '成功' : '失败' }}
              </span>
          </td>
        </tr>
      </table>

      <!-- SQL详情 -->
      <div v-if="currentNodeData.sqlId || currentNodeData.originalSql || currentNodeData.fullSql" style="margin-top: 20px;">
        <h4>SQL信息</h4>
        <el-tabs type="card">
          <el-tab-pane label="原始SQL" v-if="currentNodeData.originalSql">
            <pre class="sql-content">{{ currentNodeData.originalSql }}</pre>
          </el-tab-pane>
          <el-tab-pane label="完整SQL" v-if="currentNodeData.fullSql">
            <pre class="sql-content">{{ currentNodeData.fullSql }}</pre>
          </el-tab-pane>
        </el-tabs>
      </div>

      <!-- 参数信息 -->
      <div v-if="currentNodeData.parameters && currentNodeData.parameters.length" style="margin-top: 20px;">
        <h4>参数列表</h4>
        <table class="param-table">
          <thead>
          <tr>
            <th>参数名</th>
            <th>参数类型</th>
            <th>参数值</th>
          </tr>
          </thead>
          <tbody>
          <tr v-for="(param, index) in currentNodeData.parameters" :key="index">
            <td>{{ param.paramName }}</td>
            <td>{{ param.paramType }}</td>
            <td>{{ formatParamValue(param.paramValue) }}</td>
          </tr>
          </tbody>
        </table>
      </div>
    </div>

    <template #footer>
      <el-button @click="nodeDetailVisible = false">关闭</el-button>
    </template>
  </el-dialog>

  <!-- 参数详情弹窗 -->
  <el-dialog
          title="参数详情"
          v-model="paramDialogVisible"
          width="60%"
  >
    <div v-if="currentNodeParams && currentNodeParams.length">
      <table class="param-table">
        <thead>
        <tr>
          <th>参数名</th>
          <th>参数类型</th>
          <th>参数值</th>
        </tr>
        </thead>
        <tbody>
        <tr v-for="(param, index) in currentNodeParams" :key="index">
          <td>{{ param.paramName }}</td>
          <td>{{ param.paramType }}</td>
          <td>{{ formatParamValue(param.paramValue) }}</td>
        </tr>
        </tbody>
      </table>
    </div>
    <div v-else>
      <p>该节点没有参数信息</p>
    </div>

    <template #footer>
      <el-button @click="paramDialogVisible = false">关闭</el-button>
    </template>
  </el-dialog>
</div>

<script>
  const { createApp, ref, reactive, onMounted, nextTick } = Vue;
  const {
    ElForm, ElFormItem, ElInput, ElDatePicker, ElButton,
    ElTable, ElTableColumn, ElPagination, ElDialog,
    ElTabs, ElTabPane, ElSelect, ElOption, ElMessage,
    ElLoading
  } = ElementPlus;

  // 生成线程颜色映射表
  const generateThreadColors = (threads) => {
    const baseColors = [
      '#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6',
      '#1abc9c', '#e67e22', '#34495e', '#16a085', '#f1c40f',
      '#27ae60', '#e84393', '#d35400', '#8e44ad', '#2980b9'
    ];

    const threadColors = {};
    threads.forEach((thread, index) => {
      threadColors[thread] = baseColors[index % baseColors.length];
    });
    return threadColors;
  };

  // 提取所有线程名称
  const extractThreads = (nodes) => {
    const threads = new Set();
    const extractRecursive = (node) => {
      if (node.threadName) {
        threads.add(node.threadName);
      }
      if (node.children && node.children.length) {
        node.children.forEach(child => extractRecursive(child));
      }
    };
    nodes.forEach(node => extractRecursive(node));
    return Array.from(threads);
  };

  // 修复节点数据结构，确保父子关系完整
  const fixNodeHierarchy = (nodes, parent = null) => {
    return nodes.map(node => {
      // 为每个节点添加唯一标识（如果没有）
      if (!node.spanId) {
        node.spanId = `auto-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
      }
      // 设置父节点引用
      if (parent) {
        node.parentSpanId = parent.spanId;
      }
      // 递归处理子节点
      if (node.children && node.children.length) {
        node.children = fixNodeHierarchy(node.children, node);
      } else {
        // 确保children属性存在（即使为空）
        node.children = [];
      }
      // 确保基础属性存在
      node.serviceName = node.serviceName || '未知服务';
      node.methodName = node.methodName || '未知方法';
      node.threadName = node.threadName || '未知线程';
      node.success = node.success !== undefined ? node.success : true;
      return node;
    });
  };

  createApp({
    components: {
      ElForm, ElFormItem, ElInput, ElDatePicker, ElButton,
      ElTable, ElTableColumn, ElPagination, ElDialog,
      ElTabs, ElTabPane, ElSelect, ElOption
    },
    setup() {
      // 状态管理
      const tableData = ref([]);
      const totalCount = ref(0);
      const loading = ref(false);
      const currentPage = ref(1);

      // 弹窗控制
      const traceDialogVisible = ref(false);
      const paramDialogVisible = ref(false);
      const nodeDetailVisible = ref(false);
      const currentTraceId = ref('');

      // 数据存储
      const allTraceNodes = ref([]);
      const currentNodeParams = ref([]);
      const currentNodeData = ref(null);
      const threadColors = ref({});
      const collapsedNodes = ref(new Set());

      // D3相关变量
      let svg, g, tree, rootHierarchy;
      const tooltip = ref(null);
      const linkTooltip = ref(null);
      const copyTooltip = ref(null);
      const threadLegend = ref(null);

      // 查询参数
      const queryParams = reactive({
        size: 10,
        from: 0,
        startTime_began: null,
        startTime_end: null,
        serviceName: '',
        methodName: '',
        traceId: '',
        fullSql: ''
      });

      // 日期范围
      const dateRange = ref([]);

      // 页面加载时获取数据
      onMounted(() => {
        fetchRootNodes();
      });

      // 获取根节点列表
      const fetchRootNodes = () => {
        loading.value = true;
        let url = window.location.href.replace('/trace-list.html', '/api/trace-monitor/getRootNodes');
        // 将get改为post，参数作为请求体传递
        axios.post(url, queryParams)
                .then(response => {
                  tableData.value = response.data.traceNodeList || [];
                  totalCount.value = response.data.totalCount;
                  loading.value = false;
                })
                .catch(error => {
                  console.error('获取根节点失败:', error);
                  ElMessage.error('获取数据失败，请重试');
                  loading.value = false;
                });
      };

      // 根据traceId获取调用链
      const fetchTraceById = (traceId) => {
        const loadingInstance = ElLoading.service({
          lock: true,
          text: '加载调用链中...',
          background: 'rgba(0, 0, 0, 0.7)'
        });

        currentTraceId.value = traceId;
        collapsedNodes.value.clear();
        let url = window.location.href.replace('/trace-list.html', '/api/trace-monitor/getTraceById');
        axios.get(url, { params: { traceId } })
                .then(response => {
                  // 修复节点数据结构，确保父子关系完整
                  const rawNodes = response.data || [];
                  allTraceNodes.value = rawNodes.length ? fixNodeHierarchy(rawNodes) : [];

                  traceDialogVisible.value = true;

                  // 提取线程信息并生成颜色映射
                  const threads = extractThreads(allTraceNodes.value);
                  threadColors.value = generateThreadColors(threads);

                  // 等待弹窗渲染完成后绘制树形图
                  nextTick().then(() => {
                    drawTree(allTraceNodes.value);
                    renderThreadLegend();
                    loadingInstance.close();
                  });
                })
                .catch(error => {
                  console.error('获取调用链失败:', error);
                  ElMessage.error('获取调用链失败，请重试');
                  loadingInstance.close();
                });
      };

      // 渲染线程图例
      const renderThreadLegend = () => {
        if (!threadLegend.value) return;
        threadLegend.value.innerHTML = '';

        const title = document.createElement('div');
        title.style.fontWeight = '500';
        title.style.marginRight = '8px';
        title.textContent = '线程:';
        threadLegend.value.appendChild(title);

        Object.entries(threadColors.value).forEach(([thread, color]) => {
          const item = document.createElement('div');
          item.className = 'thread-legend-item';

          const colorDot = document.createElement('div');
          colorDot.className = 'thread-color';
          colorDot.style.backgroundColor = color;

          const text = document.createElement('span');
          const shortName = thread.split('-').pop();
          text.textContent = shortName;

          item.appendChild(colorDot);
          item.appendChild(text);
          threadLegend.value.appendChild(item);
        });
      };

      // 绘制D3树形图
      const drawTree = (nodes) => {
        // 清除现有图形
        cleanupTree();

        if (!nodes || !nodes.length) {
          // 显示空状态提示
          const container = document.getElementById('tree-container');
          container.innerHTML = '<div style="text-align: center; padding: 50px; color: #666;">没有可用的调用链数据</div>';
          return;
        }

        // 获取容器尺寸
        const container = document.getElementById('tree-container');
        const width = container.clientWidth;
        const height = container.clientHeight;

        // 清空容器
        container.innerHTML = '';

        // 创建SVG
        svg = d3.select('#tree-container')
                .append('svg')
                .attr('width', width)
                .attr('height', height)
                .call(d3.zoom().on('zoom', (event) => {
                  g.attr('transform', event.transform);
                }))
                .append('g')
                .attr('transform', 'translate(80, 80)');

        // 创建一个包含所有元素的组，用于统一缩放
        g = svg.append('g');

        // 创建树形布局
        const nodeCount = countNodes(nodes);
        const nodeSpacing = nodeCount > 50 ? 80 : 100;

        tree = d3.tree()
                .size([height - 160, width - 400])
                .separation((a, b) => (a.parent == b.parent ? 1.8 : 3) / a.depth);

        // 转换数据为层次结构（关键修复：确保所有节点被正确包含）
        rootHierarchy = d3.hierarchy(nodes[0], d => {
          // 确保子节点始终返回数组（即使为空）
          return Array.isArray(d.children) ? d.children : [];
        });

        // 更新节点的展开/收起状态
        updateNodeVisibility(rootHierarchy);

        // 计算节点位置
        tree(rootHierarchy);

        // 创建连接线组
        const linkGroup = g.append('g').attr('class', 'links');

        // 创建连接线（关键修复：确保所有父子关系都有连线）
        const link = linkGroup.selectAll('path')
                .data(rootHierarchy.links())
                .enter().append('path')
                .attr('class', d => `link ${isAsyncCall(d.source, d.target) ? 'link-async' : ''}`)
                .attr('stroke', d => {
                  return threadColors.value[d.target.data.threadName] || '#999';
                })
                .attr('d', d3.linkHorizontal()
                        .x(d => d.y)
                        .y(d => d.x))
                .on('mouseover', handleLinkMouseOver)
                .on('mouseout', handleLinkMouseOut);

        // 创建节点组（关键修复：确保所有后代节点都被渲染）
        const node = g.append('g').attr('class', 'nodes')
                .selectAll('.node')
                .data(rootHierarchy.descendants()) // 确保获取所有后代节点
                .enter().append('g')
                .attr('class', 'node')
                .attr('transform', d => `translate(${d.y},${d.x})`)
                .on('mouseover', handleMouseOver)
                .on('mouseout', handleMouseOut)
                .on('click', handleNodeClick)
                .on('dblclick', handleNodeDblClick);

        // 添加展开/收起图标（有子节点的节点）
        node.filter(d => (d.children && d.children.length > 0) || (d._children && d._children.length > 0))
                .append('text')
                .attr('class', 'node-collapse-icon')
                .attr('dy', -15)
                .attr('text-anchor', 'middle')
                .text(d => isCollapsed(d.data.spanId) ? '+' : '-');

        // 添加节点圆圈（关键修复：确保所有节点都有圆圈）
        node.append('circle')
                .attr('class', d => {
                  let className = 'node-circle ';
                  if (d.data.sqlId) {
                    className += 'node-sql';
                  } else {
                    className += d.data.success ? 'node-success' : 'node-error';
                  }
                  return className;
                })
                .attr('stroke', d => threadColors.value[d.data.threadName] || '#ccc')
                .attr('stroke-width', 2.5)
                .attr('r', d => d.depth === 0 ? 14 : 11);

        // 添加节点文本
        node.append('text')
                .attr('class', 'node-text')
                .attr('dy', d => ((d.children && d.children.length > 0) || (d._children && d._children.length > 0)) ? 40 : 35)
                .attr('text-anchor', 'middle')
                .text(d => `${d.data.serviceName}_${d.data.methodName}`)
                .on('click', (event, d) => {
                  event.stopPropagation();
                  const textToCopy = `${d.data.serviceName}_${d.data.methodName}`;
                  navigator.clipboard.writeText(textToCopy).then(() => {
                    const tooltipEl = copyTooltip.value;
                    tooltipEl.style.display = 'block';
                    tooltipEl.style.left = (event.pageX + 10) + 'px';
                    tooltipEl.style.top = (event.pageY - 10) + 'px';
                    setTimeout(() => tooltipEl.style.display = 'none', 3000);
                  }).catch(err => {
                    console.error('复制失败:', err);
                    ElMessage.error('复制失败，请手动复制');
                  });
                });
      };

      // 更新节点可见性（修复：正确处理子节点状态）
      const updateNodeVisibility = (node) => {
        if (!node) return;

        // 确保子节点数组存在
        if (!node.children && node._children) {
          node.children = node._children;
        }

        // 处理收起状态
        if (node.children && node.children.length > 0 && isCollapsed(node.data.spanId)) {
          node._children = [...node.children]; // 深拷贝保存子节点
          node.children = null;
        } else if (node._children && !isCollapsed(node.data.spanId)) {
          node.children = [...node._children]; // 恢复子节点
          node._children = null;
        }

        // 递归处理所有子节点
        if (node.children) {
          node.children.forEach(child => updateNodeVisibility(child));
        }
      };

      // 切换节点的展开/收起状态
      const toggleNode = (spanId) => {
        if (collapsedNodes.value.has(spanId)) {
          collapsedNodes.value.delete(spanId);
        } else {
          collapsedNodes.value.add(spanId);
        }
        drawTree(allTraceNodes.value);
      };

      // 检查节点是否被收起
      const isCollapsed = (spanId) => {
        return collapsedNodes.value.has(spanId);
      };

      // 判断是否为异步调用
      const isAsyncCall = (source, target) => {
        return source.data.threadName !== target.data.threadName;
      };

      // 计算节点总数（用于调试）
      const countNodes = (nodes) => {
        let count = 0;
        const countRecursive = (node) => {
          count++;
          if (node.children && node.children.length) {
            node.children.forEach(child => countRecursive(child));
          }
        };
        nodes.forEach(node => countRecursive(node));
        console.log('总节点数:', count);
        return count;
      };

      // 处理节点鼠标悬停
      const handleMouseOver = (event, d) => {
        const tooltipEl = tooltip.value;
        tooltipEl.style.display = 'block';
        tooltipEl.style.left = (event.pageX + 10) + 'px';
        tooltipEl.style.top = (event.pageY - 10) + 'px';

        let content = `<strong>${d.data.serviceName}.${d.data.methodName}</strong><br/>`;
        content += `耗时: ${d.data.costTime || '未知'}ms<br/>`;
        content += `线程: ${d.data.threadName}<br/>`;
        content += `状态: ${d.data.success ? '<span style="color: #67c23a;">成功</span>' : '<span style="color: #f56c6c;">失败</span>'}<br/>`;

        tooltipEl.innerHTML = content;
      };

      // 处理节点鼠标离开
      const handleMouseOut = () => {
        tooltip.value.style.display = 'none';
      };

      // 处理连线鼠标悬停
      const handleLinkMouseOver = (event, d) => {
        const tooltipEl = linkTooltip.value;
        tooltipEl.style.display = 'block';
        tooltipEl.style.left = (event.pageX + 10) + 'px';
        tooltipEl.style.top = (event.pageY - 10) + 'px';
        tooltipEl.innerHTML = `<strong>线程:</strong> ${d.target.data.threadName}`;
      };

      // 处理连线鼠标离开
      const handleLinkMouseOut = () => {
        linkTooltip.value.style.display = 'none';
      };

      // 处理节点单击
      const handleNodeClick = (event, d) => {
        event.stopPropagation();
        if (d.children || d._children) {
          toggleNode(d.data.spanId);
        }
      };

      // 处理节点双击
      const handleNodeDblClick = (event, d) => {
        event.stopPropagation();
        currentNodeData.value = d.data;
        nodeDetailVisible.value = true;
      };

      // 清除树形图
      const cleanupTree = () => {
        const container = document.getElementById('tree-container');
        container.innerHTML = '';
        if (tooltip.value) tooltip.value.style.display = 'none';
        if (linkTooltip.value) linkTooltip.value.style.display = 'none';
        if (copyTooltip.value) copyTooltip.value.style.display = 'none';
      };

      // 缩放控制
      const zoomIn = () => {
        if (svg) {
          svg.transition().call(d3.zoom().on('zoom', (event) => {
            g.attr('transform', event.transform);
          }).scaleBy, 1.3);
        }
      };

      const zoomOut = () => {
        if (svg) {
          svg.transition().call(d3.zoom().on('zoom', (event) => {
            g.attr('transform', event.transform);
          }).scaleBy, 0.7);
        }
      };

      const resetZoom = () => {
        if (svg) {
          svg.transition().call(d3.zoom().on('zoom', (event) => {
            g.attr('transform', event.transform);
          }).transform, d3.zoomIdentity.translate(80, 80));
        }
      };

      // 格式化日期时间
      const formatDateTime = (timestamp) => {
        if (!timestamp) return '未知时间';
        const date = new Date(timestamp);
        return date.toLocaleString('zh-CN', {
          year: 'numeric',
          month: '2-digit',
          day: '2-digit',
          hour: '2-digit',
          minute: '2-digit',
          second: '2-digit',
          fractionalSecondDigits: 3
        });
      };

      // 格式化参数值
      const formatParamValue = (value) => {
        if (typeof value === 'object' && value !== null) {
          return JSON.stringify(value, null, 2);
        }
        return value || '空';
      };

      // 处理查询
      const handleQuery = () => {
        currentPage.value = 1;
        queryParams.from = 0;
        fetchRootNodes();
      };

      // 重置查询
      const resetQuery = () => {
        Object.assign(queryParams, {
          size: 10,
          from: 0,
          startTime_began: null,
          startTime_end: null,
          serviceName: '',
          methodName: '',
          traceId: '',
          fullSql: ''
        });
        dateRange.value = [];
        currentPage.value = 1;
      };

      // 刷新数据
      const collectStats = () => {
        fetchRootNodes();
      };

      // 处理日期范围变化
      const handleDateChange = (val) => {
        if (val && val.length === 2) {
          queryParams.startTime_began = val[0].getTime();
          queryParams.startTime_end = val[1].getTime();
        } else {
          queryParams.startTime_began = null;
          queryParams.startTime_end = null;
        }
      };

      // 分页大小变化
      const handleSizeChange = (size) => {
        queryParams.size = size;
        queryParams.from = 0;
        currentPage.value = 1;
        fetchRootNodes();
      };

      // 当前页变化
      const handleCurrentChange = (page) => {
        currentPage.value = page;
        queryParams.from = (page - 1) * queryParams.size;
        fetchRootNodes();
      };

      // 查看参数
      const handleViewParams = (row) => {
        currentNodeParams.value = row.parameters || [];
        paramDialogVisible.value = true;
      };

      // 查看调用链
      const handleViewTrace = (traceId) => {
        fetchTraceById(traceId);
      };

      return {
        tableData,
        totalCount,
        loading,
        currentPage,
        queryParams,
        dateRange,
        traceDialogVisible,
        paramDialogVisible,
        nodeDetailVisible,
        currentTraceId,
        currentNodeParams,
        currentNodeData,
        tooltip,
        linkTooltip,
        copyTooltip,
        threadLegend,

        formatDateTime,
        formatParamValue,
        handleQuery,
        resetQuery,
        collectStats,
        handleDateChange,
        handleSizeChange,
        handleCurrentChange,
        handleViewParams,
        handleViewTrace,
        zoomIn,
        zoomOut,
        resetZoom,
        cleanupTree
      };
    }
  }).use(ElementPlus).mount('#app');
</script>
</body>
</html>